//  Copyright Druid Mechanics


#include "AbilitySystem/AbilityTasks/TargetDataUnderMouse.h"
#include "AbilitySystemComponent.h"
#include "Aura/Aura.h"

UTargetDataUnderMouse* UTargetDataUnderMouse::CreateTargetDataUnderMouse(UGameplayAbility* OwningAbility)
{
	/**
	 * NewAbilityTask: 针对初始化和实例化新任务的帮助函数。
	 */
	UTargetDataUnderMouse* NewObj = NewAbilityTask<UTargetDataUnderMouse>(OwningAbility);
	return NewObj;
}

void UTargetDataUnderMouse::Activate()
{
	Super::Activate();
	const bool bIsLocallyControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled();
	if (bIsLocallyControlled)
	{
		/**
		 * 将鼠标指针数据发送给服务器。
		 */
		SendMouseCursorData();
	}
	else
	{
		/**
		 * TODO: We are on the server, so listen for target data.
		 */
		const FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();
		const FPredictionKey ActivatePredictionKey = GetActivationPredictionKey();
		/**
		 * DECLARE_MULTICAST_DELEGATE_TwoParams(ServerSetReplicatedTargetData, const FGameplayAbilityTargetDataHandle&, FGameplayTag);
		 * AbilityTargetDataSetDelegate: 针对给定的Ability/PredictionKey对，返回TargetDataSet代理。
		 * NOTE: 当客户端执行ServerSetReplicatedTargetData函数并传递FGameplayAbilityTargetDataHandle时，如果服务器端已经监听了FAbilityTargetDataSetDelegate这个代理，
		 */
		AbilitySystemComponent.Get()->AbilityTargetDataSetDelegate(SpecHandle, ActivatePredictionKey).
		                       AddUObject(this, &UTargetDataUnderMouse::OnTargetDataReplicatedCallback);
		/**
		 * CallReplicatedTargetDataDelegatesIfSet: 如果已发送TargetData的Confirm/Cancel事件，则调用这些事件。
		 */
		const bool bCalledDelegate = AbilitySystemComponent.Get()->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivatePredictionKey);
		if(!bCalledDelegate)
		{
			/**
			 * SetWaitingOnRemotePlayerData:
			 * 当AT等待远程玩家数据时调用。
			 * 如果远程玩家过早结束该GA且与该GA设定相关的AT仍在运行，那么该GA将被终止。
			 */
			SetWaitingOnRemotePlayerData();
		}
	}
}

void UTargetDataUnderMouse::SendMouseCursorData()
{
	/**
	 * FScopedPredictionWindow(UAbilitySystemComponent* AbilitySystemComponent, bool CanGenerateNewKey=true);
	 * FScopedPredictionWindow: 允许局域地预测窗口的结构体。
	 * NOTE: 说白了，就是预测这个花括号内的内容。
	 */
	FScopedPredictionWindow ScopedPredictionWindow(AbilitySystemComponent.Get());
	/**
	 * FGameplayAbilityTargetData:
	 * 节点数据的通用结构体。
	 * 我们希望泛型函数生成此数据，其他泛型函数使用此数据。
	 * 我们期望能够拥有特定Actor或Object引用并且也能通用Location/Direction/Orgin information。
	 * 一些生产商示例；
	 *  -Overlap/Hit碰撞事件生成TargetData关于在近战攻击中谁打击。
	 *  -鼠标输入引发一次命中追踪，准星前方的角色会被转化为TargetData。
	 *  -鼠标输入使得TargetData从玩家(拥有者)的准星视角Origin/Direction生成。
	 *  -一个范围效果(AOE)/光环脉冲启动，以Instigator为中心一定半径内的所有角色都会被纳入TargetData。
	 *
	 * 一些使用示例:
	 *  -针对TargetData的所有Actor去Apply一个GE。
	 *  -从所有TargetData中寻找最近的Actor。
	 *  -对TargetData中的所有Actor调用某个函数。
	 *  -过滤或合并TargetData。
	 *  -在TargetData的Location孵化一个新的Actor。
	 */
	/**
	 * GetCurrentActorInfo: 获取绑定到此GA的当前Actor信息 - 仅能在实例化的能力调用。
	 */
	APlayerController* PC = Ability->GetCurrentActorInfo()->PlayerController.Get();
	FHitResult CursorHit;
	PC->GetHitResultUnderCursor(ECC_Target, false, CursorHit);
	/**
	 * FGameplayAbilityTargetDataHandle:
	 * TargetData的句柄，有两个主要用途:
	 *  -避免我们在蓝图中频繁复制完整的目标数据结构。
	 *  -让我们能够利用目标数据结构中的多态性。
	 *  -使我们能够实现NetSerialize功能，并在Clients/Server之间按值进行数据复制。
	 *
	 *  -避免使用UObject(UObject可用于在蓝图中实现多态性和按引用传递)。
	 *  -然而，在数据复制方面，使用UObject仍然会有问题。
	 *
	 *  -按值进行数据复制。
	 *  -在蓝图中按引用传递。
	 *  -目标数据结构的多态性。
	 */
	FGameplayAbilityTargetDataHandle DataHandle;
	/**
	 * GameplayAbilityTargetTypes.h: 这里面查找需要用的TargetData。
	 */
	FGameplayAbilityTargetData_SingleTargetHit* Data = new FGameplayAbilityTargetData_SingleTargetHit();
	Data->HitResult = CursorHit;
	DataHandle.Add(Data);
	FGameplayTag ApplicationTag;
	/**
	 * ServerSetReplicatedTargetData: 复制TargetData发送到服务器。
	 * 如果服务端AT在客户端的ServerSetReplicatedTargetData函数到达服务器之前执行，则就是在正常的监听ServerSetReplicatedTargetData事件，然后也需要调用CallReplicatedTargetDataDelegatesIfSet来帮忙Broadcast。
	 * 如果服务端AT在客户端的ServerSetReplicatedTargetData函数到达服务器之后执行，则就是不正常的监听，因为那时候AT还没激活，FAbilityTargetDataSetDelegate事件还没绑定函数，
	 * 所以才需要执行CallReplicatedTargetDataDelegatesIfSet来帮助确认一下，如返回False，则在调用SetWaitingOnRemotePlayerData帮助Broadcast。
	 * NOTE: AbilitySystemComponent_Abilities.cpp这里能找到函数。
	 */
	/**
	 * FPredictionKey:
	 * FPredictionKey是在GAS系统中支持客户端预测的一种通用方法。
	 * FPredictionKey本质上是一个ID，用于识别在客户端执行的预测性操作及其副作用。
	 * UAbilitySystemComponent支持FPredictionKey及其副作用在客户端与服务器之间的同步。
	 *
	 * 本质上，任何操作都可以与一个FPredictionKey关联起来，例如: 激活GA。
	 * 客户端可以生成一个全新的FPredictionKey，并在调用ServerTryActivateAbility函数时将其发送给服务器。
	 * 服务器可以确认或拒绝此次调用(ClientActivateAbilitySucceed/Failed)。
	 *
	 * 当客户端对GA进行预测时，会产生一些副作用(GE，TriggeredEvents, Animations等)。
	 * 客户端在预测这些副作用时，会将每个副作用与技能激活开始时生成的FPredictionKey关联起来。
	 *
	 * 如果GA激活被拒绝，客户端可以立即撤销这些副作用。
	 * 如果GA激活被接受，客户端必须等待复制的副作用被发送到服务器。(ClientActivatbleAbilitySucceed RPC将会立即发送，Property复制将会在几帧后发送。)
	 * 一旦服务器创建的副作用复制完成，客户端就可以撤销其本地预测产生的副作用。
	 *
	 * FPredictionKey本身主要提供以下功能:
	 * 唯一的ID和一套用于构建FPredictionKey依赖链的系统(包含Current和Base整数值)。
	 * ::NetSerialize的一种特殊实现,该实现仅将预测键序列化到进行预测的客户端。
	 * 这一点非常重要，因为它使我们能够在复制状态中对FPredictionKey进行序列化，并且只有那些向服务器提供了FPredictionKey的客户端才能真正看到这些FPredictionKey！
	 */
	/**
	 * GetActivationPredictionKey: 获取此GA的ActivationPredictionKey。
	 * void ServerSetReplicatedTargetData(FGameplayAbilitySpecHandle AbilityHandle, FPredictionKey AbilityOriginalPredictionKey, const FGameplayAbilityTargetDataHandle& ReplicatedTargetDataHandle, FGameplayTag ApplicationTag, FPredictionKey CurrentPredictionKey);
	 * AbilityOriginalPredictionKey: 与激活的GA相关联，所以一般传入GetActivationPredictionKey。
	 * CurrentPredictionKey: 可以用ASC来获取ScopedPredictionKey。
	 */
	/**
	 * ScopedPredictionKey: 当前FPredictionKey，通过FScopedPredictionWindow进行设置。
	 */
	AbilitySystemComponent->ServerSetReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey(),
	                                                      DataHandle, ApplicationTag,
	                                                      AbilitySystemComponent->ScopedPredictionKey);
	/**
	 * ShouldBroadcastAbilityTaskDelegates: 将Delegate去Broadcast到GA的Graph之前，应该调用此操作。这能确保GA仍然处于激活状态。
	 */
	if (ShouldBroadcastAbilityTaskDelegates())
	{
		/**
		 * 本地广播，告诉蓝图中的节点，所以才要判断蓝图中GA到Graph前有效。
		 */
		ValidData.Broadcast(DataHandle);
	}
}

void UTargetDataUnderMouse::OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle,
                                                           FGameplayTag ActivationTag)
{
	/**
	 * ConsumeClientReplicatedTargetData: 清空从客户端缓存的TargetData数据(仅限TargetData)。
	 */
	AbilitySystemComponent->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());
	if(ShouldBroadcastAbilityTaskDelegates())
	{
		ValidData.Broadcast(DataHandle);
	}
}
